package joodle.si;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import joodle.domain.CompositeShape;
import joodle.domain.Ellipse;
import joodle.domain.Line;
import joodle.domain.Point;
import joodle.domain.Rectangle;
import joodle.domain.Shape;
import joodle.domain.Text;
import joodle.domain.Shape.Attribute;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
 * SVGParser is used for constructing a series of shapes from the contents of an svg file
 * also it reads the attributes of each shape from the corresponding tag and applies them 
 * to the shape
 *
 */
public class SVGParser implements ShapeBuilder {
	private static final SVGParser instance = new SVGParser();
	
	private SVGParser() {}
	
	public static SVGParser instance() {
		return instance;
	}
	
	public Collection<Shape> parse(final File f) throws SVGParseException {
		Document doc;
		List<Shape> result = new ArrayList<Shape>();

		try {
			DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			doc = db.parse(f);

			NodeList chs = doc.getElementsByTagName("svg").item(0).getChildNodes();
			
			for(int i = 0; i < chs.getLength(); ++i) {
				Shape s = parseShape(chs.item(i));
				if(s != null)
					result.add(s);
			}
		}
		catch(ParserConfigurationException pce) {
			throw new SVGParseException("parser configuartion exception");
		}
		catch(SAXException se) {
			throw new SVGParseException("invlaid input file");
		}
		catch(IOException ioe) {
			throw new SVGParseException("io error");
		}
		
		return result;
	}
	/**
	 * This method reads the shape from the file
	 * @return the shape read from the file
	 */
	private static Shape parseShape(Node node) {
		String sn = node.getNodeName();
		NamedNodeMap nnm = node.getAttributes();
		String tc = node.getTextContent();
		
		Shape s = null;
		if("line".equals(sn)) {
			s = new Line();
			float x1 = Float.parseFloat(nnm.getNamedItem("x1").getNodeValue());
			float y1 = Float.parseFloat(nnm.getNamedItem("y1").getNodeValue());
			float x2 = Float.parseFloat(nnm.getNamedItem("x2").getNodeValue());
			float y2 = Float.parseFloat(nnm.getNamedItem("y2").getNodeValue());
			s.addPoint(new Point(x1, y1));
			s.addPoint(new Point(x2, y2));
		}
		else if("rect".equals(sn)) {
			s = new Rectangle();
			float x1 = Float.parseFloat(nnm.getNamedItem("x").getNodeValue());
			float y1 = Float.parseFloat(nnm.getNamedItem("y").getNodeValue());
			float x2 = x1 + Float.parseFloat(nnm.getNamedItem("width").getNodeValue());
			float y2 = y1 + Float.parseFloat(nnm.getNamedItem("height").getNodeValue());
			s.addPoint(new Point(x1, y1));
			s.addPoint(new Point(x2, y2));
		}
		else if("ellipse".equals(sn)) {
			s = new Ellipse();
			float cx = Float.parseFloat(nnm.getNamedItem("cx").getNodeValue());
			float cy = Float.parseFloat(nnm.getNamedItem("cy").getNodeValue());
			float rx = Float.parseFloat(nnm.getNamedItem("rx").getNodeValue());
			float ry = Float.parseFloat(nnm.getNamedItem("ry").getNodeValue());
			s.addPoint(new Point(cx - rx, cy - ry));
			s.addPoint(new Point(cx + rx, cy + ry));
		}
		else if("text".equals(sn)) {
			System.out.println("parsing text");
			s = new Text();
			float x = Float.parseFloat(nnm.getNamedItem("x").getNodeValue());
			float y = Float.parseFloat(nnm.getNamedItem("y").getNodeValue());
			s.addPoint(new Point(x, y));
			s.setAttributeValue(Attribute.TEXT, tc);
		}
		else if("g".equals(sn)) {
			NodeList chs = node.getChildNodes();
			CompositeShape cs = new CompositeShape();
			for(int i = 0; i < chs.getLength(); ++i) {
				Shape ss = parseShape(chs.item(i));
				if(ss != null)
					cs.addShape(ss);
			}
			if(cs.getComposingShapes().size() > 1)
				s = cs;
		}
		if(s != null) {
			parseAttributes(s, nnm);
			parseTransform(s, nnm);
		}
		
		return s;
	}
	/**
	 * Reads the attributes of the shape from the file and applies them to the shape
	 * @param s the shape to be applied
	 */
	private static void parseAttributes(Shape s, NamedNodeMap nnm) {
		if(s == null)
			return;
		
		Node n = nnm.getNamedItem("stroke");
		if(n != null) {
			String as = n.getNodeValue();
			if(as != null) {
				Color c = Color.decode(nnm.getNamedItem("stroke").getNodeValue());
				s.setAttributeValue(Shape.Attribute.COLOR, c);
			}
		}
		
		n = nnm.getNamedItem("fill");
		if(n != null) {
			String as = n.getNodeValue();
			if(as != null && !"none".equals(as)) {
				Color c = Color.decode(as);
				s.setAttributeValue(Shape.Attribute.FILL_COLOR, c);			
			}
		}
		
		n = nnm.getNamedItem("stroke-width");
		if(n != null) {
			String as = n.getNodeValue();
			if(as != null) {
				float lw = Float.parseFloat(as);
				s.setAttributeValue(Shape.Attribute.LINE_STROKE, new BasicStroke(lw));			
			}
		}
		
		Font f = null;
		n = nnm.getNamedItem("font-family");
		if(n != null) {
			String as = n.getNodeValue();
			if(as != null) {
				f = Font.decode(as);
				s.setAttributeValue(Shape.Attribute.FONT, f);			
			}
		}

		n = nnm.getNamedItem("font-size");
		if(n != null) {
			String as = n.getNodeValue();
			if(as != null) {
				f = f.deriveFont(Float.parseFloat(as));
				s.setAttributeValue(Shape.Attribute.FONT, f);			
			}
		}
}
	/**
	 * Reads the AT object from the file and creates it
	 * @param s the shape to be applied
	 */
	private static void parseTransform(Shape s, NamedNodeMap nnm) {
		Node n = nnm.getNamedItem("transform");
		if(n != null) {
			String as = n.getNodeValue();
			as = as.substring(7, as.length() - 1);
			String[] ms = as.split("\\s");
			double[] m = new double[6];
			for(int i = 0; i < 6; ++i)
				m[i] = Double.parseDouble(ms[i]);
			s.transform(new AffineTransform(m));
		}
	}

}
